package com.jt.aop;

import com.jt.anno.UseRedis;
import com.jt.util.ObjectMapperUtil;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Component;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisCluster;
import redis.clients.jedis.JedisSentinelPool;
import redis.clients.jedis.ShardedJedis;

import java.lang.reflect.Method;
import java.util.List;

/**
 * @author 张琪
 * @date 2020/12/8
 * @describe
 */

@Aspect             // 标识一个切面对象
@Component          // 需要交给Spring容器管理
public class CacheAOP {

    @Autowired
//    private Jedis jedis;        //redis单台测试
//    private ShardedJedis jedis;     redis 分片操作
//    private JedisSentinelPool jedisSentinelPool;  redis 哨兵操作
    private JedisCluster jedis;
//    bean形式的 切入点表达式
//    @Pointcut("bean(itemCatServiceImpl)")

//    within形式的 切入点表达式
//    @Pointcut("within(com.jt.service.*)")

//    execution形式的  切入点表达式
//    .* 表示一级包下的类,  ..* 表示所有子孙后代的包和类

    /**
     * execution(* com.jt.service..*.add(..))
     * 返回值类型任意, com.jt.service包下的所有类的add方法, 参数为任意类型
     * <p>
     * execution(* com.jt.service..*.*(long))
     * 返回值类型任意, com.jt,service包下的所有类的所有方法, 参数为long
     */
//    @Pointcut("execution(* com.jt.service..*.*(long))")
    @Pointcut("@annotation(com.jt.anno.UseRedis)")
    public void doTest() {
    }


    /**
     * joinPoint 代表连接点对象, 一般适用于前四个通知类型(除Around之外的)
     * 连接点对象(进入到切面中的方法) --- 程序执行的方法
     * 客人                         一般路人
     *
     * @param joinPoint 连接点对象
     */
//    @Before("doTest()")
    public void before(JoinPoint joinPoint) {
//        1. 获取目标对象
        Object o = joinPoint.getTarget();
//        2. 获取目标对象的路径  包名.类名.方法名
        String className = joinPoint.getSignature().getDeclaringTypeName();
        String methodName = joinPoint.getSignature().getName();

        System.out.println(className + "." + methodName);

//        3. 获取参数类型
        System.out.println(joinPoint.getArgs());
    }

    @Around("@annotation(useRedis)")        // 在编译后, 就相当于com.jt.anno.UseRedis
    public Object around(ProceedingJoinPoint joinPoint, UseRedis useRedis) {
        Object[] nums = joinPoint.getArgs();
        /*String key1 = joinPoint.getClass().getMethod(方法名, 参数列表数组);
         * 这种写法只能获取抽象类, 并不是方法的实现类本身, 因此不使用*/
        String key = useRedis.key() + nums[0];
        System.out.println(key);
        Object result = null;
        String json;
//        Jedis jedis = jedisSentinelPool.getResource();      // 这里使用池来获取jedis对象
        try {
            if (jedis.exists(key)) {
                json = jedis.get(key);
//                result = ObjectMapperUtil.toObject(json, Object.class);    偷鸡方法
//                5. 获取返回值类型
                MethodSignature ms = (MethodSignature) joinPoint.getSignature();
                result = ObjectMapperUtil.toObject(json, ms.getReturnType());

            } else {
                result = joinPoint.proceed();
                json = ObjectMapperUtil.toJson(result);
//                判断是否存在时间参数, 如果有的话设置生存周期
                if (useRedis.second() > 0) {
                    jedis.setex(key, useRedis.second(), json);
                } else {
                    jedis.set(key, json);
                }
            }
//            3. 判断缓存中是否有数据
        } catch (Throwable throwable) {
            throwable.printStackTrace();
        }
//        jedis.close();          // 因为使用池创建, 因此需要将资源归还给池
        return result;
    }

}
